package lar.system.opengl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.TreeMap;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import lar.system.engine.MainProgram;
import lar.system.engine.UI;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.util.Log;

public class GLString implements Drawable
{

	private int resourceId;
	private float[] color;
	private FloatBuffer vertexBuffer;	
	private String ascii;
	private float[] glPos, glDim;
	private float vertices[];
	private FloatBuffer textureBuffer;	
	private float texture[];
	private int capacity;
	private float charWidth;
	private boolean boundedWidth;
	private GLColor foregroundColor = null;
	private TreeMap<Integer, GLColor> colorCheckpoints = new TreeMap<Integer, GLColor>();
	
	public GLString(int fontResourceId, String asciiString, float[] glPos, float height)
	{
		boundedWidth = false;
		if(asciiString.length() == 0) Construct(fontResourceId, asciiString, glPos, new float[]{height,height}, asciiString.length());
		else Construct(fontResourceId, asciiString, glPos, new float[]{height*asciiString.length(),height}, asciiString.length());
	}
	
	public GLString(int fontResourceId, String asciiString, float[] glPos, float[] glDim) 
	{
		boundedWidth = true;
		Construct(fontResourceId, asciiString, new float[]{glPos[0]-glDim[0]/2, glPos[1]}, glDim, asciiString.length());
	}
	
	public GLString(int fontResourceId, String asciiString, float[] glPos, float height, int capacity)
	{
		boundedWidth = false;
		if(asciiString.length() == 0) Construct(fontResourceId, asciiString, glPos, new float[]{height,height}, capacity);
		else Construct(fontResourceId, asciiString, glPos, new float[]{height*asciiString.length(),height}, capacity);
	}
	
	/**
	 * When rendering, color after @idx will change to @color
	 * @param idx index in the string. Starts from 0
	 * @param color Color object
	 */
	public void setColorChangeAt(int idx, GLColor color)
	{
		if(colorCheckpoints.containsKey(idx)) colorCheckpoints.remove(idx);
		if(color != null) colorCheckpoints.put(idx, color);
	}
	
	//default constructor
	private void Construct(int fontResourceId, String asciiString, float[] glPos, float[] glDim, int capacity) 
	{
		this.resourceId = fontResourceId;
		if(capacity == 0) capacity = 1;
		ascii = asciiString;
		charWidth = glDim[0]/(ascii.length() == 0 ? 1 : ascii.length());
		color = null;
		this.capacity = capacity;
		vertices = new float[capacity*12];
		texture = new float[capacity*8]; 
		
		byte[] arr = asciiString.getBytes();
		
		this.glDim = new float[]{glDim[0], glDim[1]};
		this.glPos = new float[]{glPos[0], glPos[1]};
		//set texture coordinates
		float unit = 0.0625f;
		for(int i = 0; i < asciiString.length()*8; i += 8)
		{
			float x = (float)(((int)arr[i/8] & 0xff)%16)*unit;
			float y = (float)(((int)arr[i/8] & 0xff)/16)*unit;
			//axis center is at (0,1) pointing right, down
			texture[0+i] = x; texture[1+i] = y+unit; //bottom left
			texture[2+i] = x; texture[3+i] = y; //top left
			texture[4+i] = x+unit; texture[5+i] = y+unit; //bottom right
			texture[6+i] = x+unit; texture[7+i] = y; //top right
		}
		//set vertex coordinates
		for(int i = 0; i < capacity*12; i += 12)
		{
			vertices[0 +i] = -1.0f; vertices[1 +i] = -1.0f; vertices[2 +i] =  0.0f;
			vertices[3 +i] = -1.0f; vertices[4 +i] =  1.0f; vertices[5 +i] =  0.0f;
			vertices[6 +i] =  1.0f; vertices[7 +i] = -1.0f; vertices[8 +i] =  0.0f;
			vertices[9 +i] =  1.0f; vertices[10+i] =  1.0f; vertices[11+i] =  0.0f;
		}
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
		byteBuffer.order(ByteOrder.nativeOrder());
		
		vertexBuffer = byteBuffer.asFloatBuffer();
		
		vertexBuffer.put(vertices);
		
		vertexBuffer.position(0);
		
		byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
		byteBuffer.order(ByteOrder.nativeOrder());
		textureBuffer = byteBuffer.asFloatBuffer();
		textureBuffer.put(texture);
		textureBuffer.position(0);
	}

	public void setForegroundColor(GLColor c)
	{
		this.foregroundColor = c;
	}
	
	public void centerAroundPoint(float x, float y)
	{
		glPos[0] = x - ((this.ascii.length()-1)/2f)*charWidth;
		glPos[1] = y;
	}
	
	private void updateTexCoords()
	{
		float unit = 0.0625f;
		byte[] arr = ascii.getBytes();
		//Log.d("R", "capacity: " + capacity + " arraylength: " + texture.length);
		for(int i = 0; i < ascii.length()*8; i += 8)
		{
			float x = (float)(((int)arr[i/8] & 0xff)%16)*unit;
			float y = (float)(((int)arr[i/8] & 0xff)/16)*unit;
			//axis center is at (0,1) pointing right, down
			texture[0+i] = x; texture[1+i] = y+unit; //bottom left
			texture[2+i] = x; texture[3+i] = y; //top left
			texture[4+i] = x+unit; texture[5+i] = y+unit; //bottom right
			texture[6+i] = x+unit; texture[7+i] = y; //top right
		}
		ByteBuffer byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
		byteBuffer.order(ByteOrder.nativeOrder());
		textureBuffer = byteBuffer.asFloatBuffer();
		textureBuffer.put(texture);
		textureBuffer.position(0);
	}
	
	private void expand()
	{
		float cw = charWidth;
		float[] preserve_color = color;
		Construct(resourceId, ascii, glPos, glDim, capacity*2);
		setColor(preserve_color);
		charWidth = cw;
	}
	
	public synchronized void SetString(String str)
	{
		while(str.length() > capacity) expand();
		if(!boundedWidth) glDim[0] = str.length()*charWidth;
		ascii = new String(str);
		updateTexCoords();
		UI.needRendering = true;
	}
	
	public synchronized void Draw(GL11 gl) 
	{
		gl.glEnableClientState(GL11.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
		if(this.foregroundColor != null && ascii.length() > 0)
		{
			gl.glBindTexture(GL11.GL_TEXTURE_2D, -1);
			gl.glFrontFace(GL11.GL_CW);
			gl.glVertexPointer(3, GL11.GL_FLOAT, 0, vertexBuffer);
			gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, textureBuffer);
			gl.glPushMatrix();
			gl.glTranslatef(glPos[0] + 0.5f*charWidth*(ascii.length()-1), glPos[1], -1.0f);
			gl.glScalef(glDim[0]/2, glDim[1], 1.0f);
			foregroundColor.apply(gl);
			gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
			foregroundColor.restore(gl);
			gl.glPopMatrix();
		}
		gl.glBindTexture(GL10.GL_TEXTURE_2D, MainProgram.glSurfaceView.renderer.ResourceIdToTexture(resourceId));
		
		//gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		//gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		
		gl.glFrontFace(GL10.GL_CW);
		
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
		
		float unit_len = (float)ascii.length();
		float unit = charWidth;
		//Log.d("R", "" + unit);
		
		if(this.color != null) gl.glColor4f(this.color[0], this.color[1], this.color[2], this.color[3]);
		
		for(int i = 0; i < ascii.length(); i++)
		{		
			gl.glPushMatrix();
			gl.glTranslatef(glPos[0]+unit*i, glPos[1], -1.0f);
			gl.glScalef(charWidth, glDim[1], 1.0f);
			GLColor mapping = colorCheckpoints.get(i);
			if(mapping != null) mapping.apply(gl);
			gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4*i, 4);
			gl.glPopMatrix();
		}
		gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
	}

	public synchronized String getAsciiString()
	{
		return ascii;
	}
	
	public synchronized void setColor(float[] color)
	{
		this.color = color;
		UI.needRendering = true;
	}
	
	public synchronized float[] getColor()
	{
		return this.color;
	}
	
	public boolean CheckBounds(float x, float y) 
	{
		/*
		 * No bounds checking.
		 */
		return false;
	}
	

}
